/**
 * Copyright © 2015 digitalfondue (info@digitalfondue.ch)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.nulldev.util.web.flexibleKit.jfvParse;

import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.data.Arrays.arrays.FastLists;

/**
 * Represent a document.
 */
public class Document extends Node {
	private final List<Node> childNodes = FastLists.list(2);
	private volatile DocumentType doctype;

	@Override
	public byte getNodeType() {
		return DOCUMENT_NODE;
	}

	@Override
	public List<Node> getChildNodes() {
		return Collections.unmodifiableList(childNodes);
	}

	@Override
	List<Node> getMutableChildNodes() {
		return childNodes;
	}

	@Override
	public int getChildCount() {
		return this.childNodes.size();
	}

	public void setDoctype(DocumentType doctype) {
		this.doctype = doctype;
	}

	public DocumentType getDoctype() {
		return doctype;
	}

	public Element getDocumentElement() {
		return getFirstElementChild();
	}

	private Element getChildOfDocumentElementMatching(Predicate<String> nodeNameMatcher) {
		Element e = getFirstElementChild();
		if (e != null) {
			for (Node c : e.getChildNodes()) {
				if (c instanceof Element && nodeNameMatcher.test(c.getNodeName()) && NAMESPACE_HTML.equals(((Element) c).getNamespaceURI())) {
					return (Element) c;
				}
			}
		}
		return null;
	}

	/**
	 * @return the "head" element if present
	 */
	public Element getHead() {
		return getChildOfDocumentElementMatching("head"::equals);
	}

	/**
	 * @return return the "body" or "frameset" element if present
	 */
	public Element getBody() {
		return getChildOfDocumentElementMatching(n -> "body".equals(n) || "frameset".equals(n));
	}

	/**
	 * Replace the current body. TODO: need to check if it's a body|frameset element
	 * in html namespace, handle the case when there is no body too
	 *
	 * @param element
	 */
	public void setBody(Element element) {
		Element e = getBody();
		if (element != null && e != null && e.parentNode != null) {
			e.parentNode.replaceChild(element, e);
		}
	}

	/**
	 * Return the {@link String} "#document".
	 */
	@Override
	public String getNodeName() {
		return "#document";
	}

	@Override
	public Node cloneNode(boolean deep) {
		Document cloned = new Document();
		if (doctype != null) {
			cloned.doctype = (DocumentType) doctype.cloneNode(true);
			cloned.doctype.parentNode = this;
		}
		if (!deep) {
			return cloned;
		}
		for (Node child : childNodes) {
			Node clonedChild = child.cloneNode(true);
			clonedChild.parentNode = cloned;
			cloned.childNodes.add(clonedChild);
		}
		return cloned;
	}

	public void clear() {
		this.childNodes.clear();
	}

	@Override
	public String toString() {
		return "Document[nodes=" + this.childNodes.size() + ",hash=" + MathUtil.toHex(this.hashCode()) + "]";
	}
}
